home *** CD-ROM | disk | FTP | other *** search
/ PC World 2008 September / PCWorld_2008-09_cd.bin / v cisle / sadanastroju / lightning-0.8-tb-win.xpi / js / calItipProcessor.js < prev    next >
Text File  |  2008-03-14  |  19KB  |  479 lines

  1. /* -*- Mode: javascript; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
  2. /* ***** BEGIN LICENSE BLOCK *****
  3.  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  4.  *
  5.  * The contents of this file are subject to the Mozilla Public License Version
  6.  * 1.1 (the "License"); you may not use this file except in compliance with
  7.  * the License. You may obtain a copy of the License at
  8.  * http://www.mozilla.org/MPL/
  9.  *
  10.  * Software distributed under the License is distributed on an "AS IS" basis,
  11.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  12.  * for the specific language governing rights and limitations under the
  13.  * License.
  14.  *
  15.  * The Original Code is Simdesk Technologies code.
  16.  *
  17.  * The Initial Developer of the Original Code is Simdesk Technologies Inc.
  18.  * Portions created by the Initial Developer are Copyright (C) 2007
  19.  * the Initial Developer. All Rights Reserved.
  20.  *
  21.  * Contributor(s):
  22.  *   Clint Talbert <ctalbert.moz@gmail.com>
  23.  *   Eva Or <evaor1012@yahoo.ca>
  24.  *   Matthew Willis <lilmatt@mozilla.com>
  25.  *
  26.  * Alternatively, the contents of this file may be used under the terms of
  27.  * either the GNU General Public License Version 2 or later (the "GPL"), or
  28.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  29.  * in which case the provisions of the GPL or the LGPL are applicable instead
  30.  * of those above. If you wish to allow use of your version of this file only
  31.  * under the terms of either the GPL or the LGPL, and not to allow others to
  32.  * use your version of this file under the terms of the MPL, indicate your
  33.  * decision by deleting the provisions above and replace them with the notice
  34.  * and other provisions required by the GPL or the LGPL. If you do not delete
  35.  * the provisions above, a recipient may use your version of this file under
  36.  * the terms of any one of the MPL, the GPL or the LGPL.
  37.  *
  38.  * ***** END LICENSE BLOCK ***** */
  39.  
  40.  
  41. // Operations on the calendar
  42. const CAL_ITIP_PROC_ADD_OP = 1;
  43. const CAL_ITIP_PROC_UPDATE_OP = 2;
  44. const CAL_ITIP_PROC_DELETE_OP = 3;
  45.  
  46. /**
  47.  * Constructor of calItipItem object
  48.  */
  49. function calItipProcessor() {
  50.     this.wrappedJSObject = this;
  51.     this._handledID = null;
  52. }
  53.  
  54. calItipProcessor.prototype = {
  55.     getInterfaces: function cipGI(count) {
  56.         var ifaces = [
  57.             Components.interfaces.nsIClassInfo,
  58.             Components.interfaces.nsISupports,
  59.             Components.interfaces.calIItipProcessor
  60.         ];
  61.         count.value = ifaces.length;
  62.         return ifaces;
  63.     },
  64.  
  65.     getHelperForLanguage: function cipGHFL(aLanguage) {
  66.         return null;
  67.     },
  68.  
  69.     contractID: "@mozilla.org/calendar/itip-processor;1",
  70.     classDescription: "Calendar iTIP processor",
  71.     classID: Components.ID("{9787876b-0780-4464-8282-b7f86fb221e8}"),
  72.     implementationLanguage: Components.interfaces.nsIProgrammingLanguage.JAVASCRIPT,
  73.     flags: 0,
  74.  
  75.     QueryInterface: function cipQI(aIid) {
  76.         if (!aIid.equals(Components.interfaces.nsIClassInfo) &&
  77.             !aIid.equals(Components.interfaces.nsISupports) &&
  78.             !aIid.equals(Components.interfaces.calIItipProcessor))
  79.         {
  80.             throw Components.results.NS_ERROR_NO_INTERFACE;
  81.         }
  82.  
  83.         return this;
  84.     },
  85.  
  86.     mIsUserInvolved: false,
  87.     get isUserInvolved() {
  88.         return this.mIsUserInvolved;
  89.     },
  90.     set isUserInvolved(aValue) {
  91.         return (this.mIsUserInvolved = aValue);
  92.     },
  93.  
  94.     /**
  95.      * Processes the given calItipItem based on the settings inside it.
  96.      * @param calIItipItem  A calItipItem to process.
  97.      * @param calIOperationListener A calIOperationListener to return status
  98.      * @return boolean  Whether processing succeeded or not.
  99.      */
  100.     processItipItem: function cipPII(aItipItem, aListener) {
  101.         // Sanity check the input
  102.         if (!aItipItem) {
  103.             throw new Components.Exception("processItipItem: " +
  104.                                            "Invalid or non-existant " +
  105.                                            "itipItem passed in.",
  106.                                            Components.results.NS_ERROR_INVALID_ARG);
  107.         }
  108.  
  109.         // Clone the passed in itipItem like a sheep.
  110.         var respItipItem = aItipItem.clone();
  111.  
  112.         var recvMethod = respItipItem.receivedMethod;
  113.         respItipItem.responseMethod = this._suggestResponseMethod(recvMethod);
  114.         var respMethod = respItipItem.responseMethod;
  115.  
  116.         var autoResponse = respItipItem.autoResponse;
  117.         var targetCalendar = respItipItem.targetCalendar;
  118.  
  119.         // Sanity checks using the first item
  120.         var itemList = respItipItem.getItemList({ });
  121.         var calItem = itemList[0];
  122.         if (!calItem) {
  123.             throw new Error ("processItipItem: " +
  124.                              "getFirstItem() found no items!");
  125.         }
  126.  
  127.         var calItemType = this._getCalItemType(calItem);
  128.         if (!calItemType) {
  129.             throw new Error ("processItipItem: " +
  130.                              "_getCalItemType() found no item type!");
  131.         }
  132.  
  133.         // Sanity check that mRespMethod is a valid response per the spec.
  134.         if (!this._isValidResponseMethod(recvMethod, respMethod, calItemType)) {
  135.             throw new Error ("processItipItem: " +
  136.                              "_isValidResponseMethod() found an invalid " +
  137.                              "response method: " + respMethod);
  138.         }
  139.  
  140.         // Check to see if we have an existing item or not, then continue
  141.         // processing appropriately
  142.         var i =0;
  143.         while (calItem) {
  144.             this._isExistingItem(calItem, recvMethod, respMethod, targetCalendar,
  145.                                  aListener);
  146.             ++i;
  147.             calItem = itemList[i];
  148.         }
  149.  
  150.         // Send the appropriate response
  151.         // figure out a good way to determine when a response is needed!
  152.         if (recvMethod != respMethod) {
  153.             this._getTransport().simpleSendResponse(respItipItem);
  154.         }
  155.     },
  156.  
  157.     /* Continue processing the iTip Item now that we have determined whether
  158.      * there is an existing item or not.
  159.      */
  160.     _continueProcessingItem: function cipCPI(newItem, existingItem, recvMethod, respMethod,
  161.                                              calAction, targetCalendar, aListener) {
  162.         var transport = this._getTransport();
  163.         switch (recvMethod) {
  164.             case "REQUEST":
  165.                 // Only add to calendar if we accepted invite
  166.                 var replyStat = this._getReplyStatus(newItem,
  167.                                                      transport.defaultIdentity);
  168.                 if (replyStat == "DECLINED") {
  169.                     break;
  170.                 }
  171.                 // else fall through
  172.             case "PUBLISH":
  173.                 if (!this._processCalendarAction(newItem,
  174.                                                  existingItem,
  175.                                                  calAction,
  176.                                                  targetCalendar,
  177.                                                  aListener))
  178.                 {
  179.                     throw new Error ("processItipItem: " +
  180.                                      "_processCalendarAction failed!");
  181.                 }
  182.                 break;
  183.             case "REPLY":
  184.             case "REFRESH":
  185.             case "ADD":
  186.             case "CANCEL":
  187.             case "COUNTER":
  188.             case "DECLINECOUNTER":
  189.                 throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
  190.  
  191.             default:
  192.                 throw new Error("processItipItem: " +
  193.                                 "Received unknown method: " +
  194.                                 recvMethod);
  195.         }
  196.  
  197.         // When replying, the reply must only contain the ORGANIZER and the
  198.         // status of the ATTENDEE that represents ourselves. Therefore we must
  199.         // remove all other ATTENDEEs from the itipItem we send back.
  200.         if (respMethod == "REPLY") {
  201.             // Get the id that represents me.
  202.             // XXX Note that this doesn't take into consideration invitations
  203.             //     sent to email aliases. (ex: lilmatt vs mwillis)
  204.             var me;
  205.             var idPrefix;
  206.             if (transport.type == "email") {
  207.                 me = transport.defaultIdentity;
  208.                 idPrefix = "mailto:";
  209.             } else {
  210.                 throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
  211.             }
  212.  
  213.             var attendees = newItem.getAttendees({});
  214.  
  215.             for each (var attendee in attendees) {
  216.                 // Leave the ORGANIZER alone.
  217.                 if (!attendee.isOrganizer) {
  218.                     // example: mailto:joe@domain.com
  219.                     var meString = idPrefix + me;
  220.                     if (attendee.id.toLowerCase() != meString.toLowerCase()) {
  221.                         newItem.removeAttendee(attendee);
  222.                     }
  223.                 }
  224.             }
  225.         }
  226.     },
  227.  
  228.  
  229.     /**
  230.      * @return integer  The next recommended iTIP state.
  231.      */
  232.     _suggestResponseMethod: function cipSRM(aRecvMethod) {
  233.         switch (aRecvMethod) {
  234.             case "REQUEST":
  235.                 return "REPLY";
  236.  
  237.             case "REFRESH":
  238.             case "COUNTER":
  239.                 return "REQUEST";
  240.  
  241.             case "PUBLISH":
  242.             case "REPLY":
  243.             case "ADD":
  244.             case "CANCEL":
  245.             case "DECLINECOUNTER":
  246.                 return aRecvMethod;
  247.  
  248.             default:
  249.                 throw new Error("_suggestResponseMethod: " +
  250.                                 "Received unknown method: " +
  251.                                 aRecvMethod);
  252.         }
  253.     },
  254.  
  255.     /**
  256.      * Given mRecvMethod and mRespMethod, this checks that mRespMethod is
  257.      * valid according to the spec.
  258.      *
  259.      * @return boolean  Whether or not mRespMethod is valid.
  260.      */
  261.     _isValidResponseMethod: function cipIAR(aRecvMethod,
  262.                                             aRespMethod,
  263.                                             aCalItemType) {
  264.         switch (aRecvMethod) {
  265.             // We set response to ADD automatically, but if the GUI did not
  266.             // find the event the user may set it to REFRESH as per the spec.
  267.             // These are the only two valid responses.
  268.             case "ADD":
  269.                 if (!(aRespMethod == "ADD" ||
  270.                      (aRespMethod == "REFRESH" &&
  271.                      // REFRESH is not a valid response to an ADD for VJOURNAL
  272.                      (aCalItemType == Components.interfaces.calIEvent ||
  273.                       aCalItemType == Components.interfaces.calITodo))))
  274.                 {
  275.                     return false;
  276.                 }
  277.                 break;
  278.  
  279.             // Valid responses to COUNTER are REQUEST or DECLINECOUNTER.
  280.             case "COUNTER":
  281.                 if (!(aRespMethod == "REQUEST" ||
  282.                       aRespMethod == "DECLINECOUNTER"))
  283.                 {
  284.                     return false;
  285.                 }
  286.                 break;
  287.  
  288.             // Valid responses to REQUEST are:
  289.             //     REPLY   (accept or error)
  290.             //     REQUEST (delegation, inviting someone else)
  291.             //     COUNTER (propose a change)
  292.             case "REQUEST":
  293.                 if (!(aRespMethod == "REPLY" ||
  294.                       aRespMethod == "REQUEST" ||
  295.                       aRespMethod == "COUNTER"))
  296.                 {
  297.                     return false;
  298.                 }
  299.                 break;
  300.  
  301.             // REFRESH should respond with a request
  302.             case "REFRESH":
  303.                 if (aRespMethod == "REQUEST") {
  304.                     return false;
  305.                 }
  306.                 break;
  307.  
  308.             // The rest are easiest represented as:
  309.             //     (aRecvMethod != aRespMethod) == return false
  310.             case "PUBLISH":
  311.             case "CANCEL":
  312.             case "REPLY":
  313.             case "PUBLISH":
  314.             case "DECLINECOUNTER":
  315.                 if (aRespMethod != aRecvMethod) {
  316.                     return false;
  317.                 }
  318.                 break;
  319.  
  320.             default:
  321.                 throw new Error("_isValidResponseMethod: " +
  322.                                 "Received unknown method: " +
  323.                                 aRecvMethod);
  324.         }
  325.  
  326.         // If we got to here, then the combination is valid.
  327.         return true;
  328.     },
  329.  
  330.     /**
  331.      * Helper to return whether an item is an event, todo, etc.
  332.      */
  333.     _getCalItemType: function cipGCIT(aCalItem) {
  334.         if (aCalItem instanceof Components.interfaces.calIEvent) {
  335.             return Components.interfaces.calIEvent;
  336.         } else if (aCalItem instanceof Components.interfaces.calITodo) {
  337.             return Components.interfaces.calITodo;
  338.         }
  339.  
  340.         throw new Error ("_getCalItemType: " +
  341.                          "mCalItem item type is unknown");
  342.     },
  343.  
  344.     /**
  345.      * This performs the actual add/update/delete of an event on the user's
  346.      * calendar.
  347.      */
  348.     _processCalendarAction: function cipPCA(aCalItem,
  349.                                             aExistingItem,
  350.                                             aOperation,
  351.                                             aTargetCalendar,
  352.                                             aListener) {
  353.         switch (aOperation) {
  354.             case CAL_ITIP_PROC_ADD_OP:
  355.                 aTargetCalendar.addItem(aCalItem, aListener);
  356.  
  357.                 // XXX Change this to reflect the success or failure of adding
  358.                 //     the item to the calendar.
  359.                 return true;
  360.  
  361.             case CAL_ITIP_PROC_UPDATE_OP:
  362.                 // To udpate, we must require the existing item to be set
  363.                 if (!aExistingItem)
  364.                     throw new Error("_processCalendarAction: Item to update not found");
  365.  
  366.                 // TODO: Handle generation properly - Bug 418345 
  367.                 aCalItem.generation = aExistingItem.generation;
  368.                 // We also have to ensure that the calendar is set properly on
  369.                 // the new item, or items with alarms will throw during the
  370.                 // notification process
  371.                 aCalItem.calendar = aExistingItem.calendar;
  372.                 aTargetCalendar.modifyItem(aCalItem, aExistingItem, aListener);
  373.                 return true;
  374.  
  375.             case CAL_ITIP_PROC_DELETE_OP:
  376.                 throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
  377.  
  378.             default:
  379.                 throw new Error("_processCalendarAction: " +
  380.                                 "Undefined Operation: " + aOperation);
  381.         }
  382.  
  383.         // If you got to here, something went horribly, horribly wrong.
  384.         return false;
  385.     },
  386.  
  387.     /**
  388.      * Helper function to get the PARTSTAT value from the given calendar
  389.      * item for the specified attendee
  390.      */
  391.     _getReplyStatus: function cipGRS(aCalItem, aAttendeeId) {
  392.         var idPrefix = "mailto:";
  393.  
  394.         // example: mailto:joe@domain.com
  395.         var idString = idPrefix + aAttendeeId;
  396.         var attendee = aCalItem.getAttendeeById(idString);
  397.         if (!attendee) {
  398.             // Bug 420516 -- we don't support delegation yet TODO: Localize this?
  399.             throw new Error("_getReplyStatus: " +
  400.                             "You are not on the list of invited attendees, delegation " +
  401.                             "is not supported yet.  See bug 420516 for details.");
  402.             
  403.         }
  404.         
  405.         return attendee.participationStatus;
  406.     },
  407.  
  408.     // A placeholder to make sure we don't try to add multiple items from the
  409.     // onOperationComplete function.
  410.     _handledID:null,
  411.  
  412.     /**
  413.      * Helper function to determine if this item already exists on this calendar
  414.      * or not.  It then calls _continueProcessingItem setting calAction and
  415.      * existingItem appropirately
  416.      */
  417.     _isExistingItem: function cipIEI(aCalItem, aRecvMethod, aRespMethod,
  418.                                      aTargetCal, aListener) {
  419.         
  420.         var foundItemListener = {
  421.             itipProcessor: this,
  422.             onOperationComplete:
  423.             function (aCalendar, aStatus, aOperationType, aId, aDetail) {
  424.                 // If there is no item, we get this call with a failure error
  425.                 if (!Components.isSuccessCode(aStatus) &&
  426.                     !this.itipProcessor._handledID) {
  427.                     // Cache the id so that we know we've handled this one.
  428.                     this.itipProcessor._handledID = aCalItem.id;
  429.                     this.itipProcessor._continueProcessingItem(aCalItem,
  430.                                                                null,
  431.                                                                aRecvMethod,
  432.                                                                aRespMethod,
  433.                                                                CAL_ITIP_PROC_ADD_OP,
  434.                                                                aTargetCal,
  435.                                                                aListener);
  436.                 }
  437.             },
  438.             onGetResult:
  439.             function onget(aCalendar, aStatus, aItemType, aDetail, aCount, aItems) {
  440.                 // Now, if the old item exists, cache it and return true
  441.                 if (aCount && aItems[0]) {
  442.                     this.itipProcessor._continueProcessingItem(aCalItem,
  443.                                                                aItems[0],
  444.                                                                aRecvMethod,
  445.                                                                aRespMethod,
  446.                                                                CAL_ITIP_PROC_UPDATE_OP,
  447.                                                                aTargetCal,
  448.                                                                aListener);
  449.                 }
  450.             }
  451.         };
  452.         if (aTargetCal) {
  453.             aTargetCal.getItem(aCalItem.id, foundItemListener);
  454.         } else {
  455.             // Then we do not have a target calendar to search,
  456.             // this is probably a DECLINE reply or some other such response,
  457.             // allow it to pass through
  458.             this._continueProcessingItem(aCalItem, null, aRecvMethod, aRespMethod,
  459.                                          null, aTargetCal, aListener);
  460.         }
  461.     },
  462.  
  463.     /**
  464.      * Centralized location for obtaining the proper transport.  This way it
  465.      * will be easy to support multiple transports in the future
  466.      * without changing the existing code too much.
  467.      */
  468.     _getTransport: function cipGT() {
  469.         // XXX Support for transports other than email go here.
  470.         //     For now we just assume it's email.
  471.         var transport = Components.classes["@mozilla.org/calendar/itip-transport;1?type=email"].
  472.                         createInstance(Components.interfaces.calIItipTransport);
  473.         if (!transport) {
  474.             throw new Error("iTipProcessor cannot instantiate transport");
  475.         }
  476.         return transport;
  477.     }
  478. }
  479.